//
// SslPlay.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
// Copyright (c) Microsoft Corporation. Licensed under the MIT license.
//

#include <iostream>
#include <stdio.h>
#include <string.h>

#include <openssl/ecdsa.h>
#include <openssl/obj_mac.h>
#include <openssl/kdf.h>
#include "e_scossl.h"

#if OPENSSL_VERSION_MAJOR == 3
#include <openssl/provider.h>
#endif

BIO *bio_err = NULL;

// By default exit Sslplay application if an error is encountered
#define SSLPLAY_EXIT_ON_ERROR (1)

void handleError(const char* description)
{
    printf("Failure:%s:\n", description);
#if SSLPLAY_EXIT_ON_ERROR
    exit(1);
#endif
}

void handleOpenSSLError(const char* description)
{
    printf("Failure:%s:\n", description);
    BIO_printf(bio_err, "OpenSSL Error:\n");
    ERR_print_errors(bio_err);
#if SSLPLAY_EXIT_ON_ERROR
    exit(1);
#endif
}

const char SeparatorLine[] = "-----------------------------------------------------------------------------------------\n";

void printBytes(char* data, int len, const char* header)
{
    printf("\n%s (%d bytes): ", header, len);
    for (int i = 0; i < len; i++)
    {
         printf("%X",data[i]);
    }
    printf("\n\n");
}

// Largest supported curve is P521 => 66 * 2 + 4 (int headers) + 3 (seq header)
#define SCOSSL_ECDSA_MAX_DER_SIGNATURE_LEN (139)

void TestEcdsa(EC_KEY* key)
{
    unsigned char testHash[] = {
        0x1, 0x54, 0xF, 0x2, 0xC9, 0xC, 0xFF, 0x31,
        0x1, 0x54, 0xF, 0x2, 0xC9, 0xC, 0xFF, 0x31,
        0x1, 0x54, 0xF, 0x2, 0xC9, 0xC, 0xFF, 0x31,
        0x1, 0x54, 0xF, 0x2, 0xC9, 0xC, 0xFF, 0x31
    };
    unsigned char resultBytes[SCOSSL_ECDSA_MAX_DER_SIGNATURE_LEN] = { 0 };
    unsigned int signatureBytesCount = sizeof(resultBytes);
    ECDSA_SIG* ecdsaSig = NULL;
    printf("Command ECDSA_sign\n");
    if( !ECDSA_sign(0, testHash, sizeof(testHash), resultBytes, &signatureBytesCount, key) )
    {
        handleOpenSSLError("ECDSA_sign failed\n");
        goto end;
    }
    printf("Command ECDSA_verify\n");
    if( ECDSA_verify(0, testHash, sizeof(testHash), resultBytes, signatureBytesCount, key) != 1 )
    {
        handleOpenSSLError("ECDSA_verify failed\n");
        goto end;
    }
    else
    {
        printf("ECDSA_verify Succeeded\n");
    }
    printf("Command ECDSA_do_sign\n");
    ecdsaSig = ECDSA_do_sign(testHash, sizeof(testHash), key);
    if( ecdsaSig == NULL )
    {
        handleOpenSSLError("ECDSA_do_sign failed\n");
        goto end;
    }
    printf("Command ECDSA_do_verify\n");
    if( ECDSA_do_verify(testHash, sizeof(testHash), ecdsaSig, key) != 1 ){
        handleOpenSSLError("ECDSA_do_verify failed\n");
        goto end;
    }
    else
    {
        printf("ECDSA_do_verify Succeeded\n");
    }
end:
    if( ecdsaSig )
        ECDSA_SIG_free(ecdsaSig);
    return;
}

// Smallest supported curve is P256 => 32 * 2 byte shared secret is largest secret that can be generated by all curves
#define SCOSSL_ECDH_SHARED_SECRET_LEN (64)

void TestEcdh(EC_KEY* key1, EC_KEY* key2)
{
    unsigned char sharedSecretBytes1[SCOSSL_ECDH_SHARED_SECRET_LEN] = { 0 };
    unsigned char sharedSecretBytes2[SCOSSL_ECDH_SHARED_SECRET_LEN] = { 0 };
    const EC_POINT* publicKey1 = EC_KEY_get0_public_key(key1);
    const EC_POINT* publicKey2 = EC_KEY_get0_public_key(key2);

    printf("Command ECDH_compute_key #1\n");
    if( ECDH_compute_key(sharedSecretBytes1, SCOSSL_ECDH_SHARED_SECRET_LEN, publicKey2, key1, NULL) <= 0 )
    {
        handleOpenSSLError("ECDH_compute_key failed\n");
        goto end;
    }

    printf("Command ECDH_compute_key #2\n");
    if( ECDH_compute_key(sharedSecretBytes2, SCOSSL_ECDH_SHARED_SECRET_LEN, publicKey1, key2, NULL) <= 0 )
    {
        handleOpenSSLError("ECDH_compute_key failed\n");
        goto end;
    }

    if (memcmp(sharedSecretBytes1, sharedSecretBytes2, SCOSSL_ECDH_SHARED_SECRET_LEN) != 0)
    {
        handleError("Shared secrets don't match\n");
        goto end;
    }
    else
    {
        printf("ECDH_compute_key Succeeded\n");
    }
end:
    return;
}

void TestEccCurve(int nid)
{
    EC_KEY* key1 = NULL;
    EC_KEY* key2 = NULL;
    printf("Command EC_KEY_new_by_curve_name\n");
    key1 = EC_KEY_new_by_curve_name(nid);
    key2 = EC_KEY_new_by_curve_name(nid);
    printf("Command EC_KEY_generate_key\n");
    EC_KEY_generate_key(key1);
    EC_KEY_generate_key(key2);
    TestEcdsa(key1);
    TestEcdh(key1, key2);
    EC_KEY_free(key1);
    EC_KEY_free(key2);
    return;
}

void TestEcc()
{
    TestEccCurve(NID_secp192k1);
    TestEccCurve(NID_secp224r1);
    TestEccCurve(NID_X9_62_prime256v1);
    TestEccCurve(NID_secp384r1);
    TestEccCurve(NID_secp521r1);
    TestEccCurve(NID_brainpoolP256r1);
    TestEccCurve(NID_brainpoolP384r1);
    TestEccCurve(NID_brainpoolP512r1);
    TestEccCurve(NID_brainpoolP256t1);
    TestEccCurve(NID_brainpoolP384t1);
    TestEccCurve(NID_brainpoolP512t1);
    printf("%s", SeparatorLine);
}

// Largest supported dlgroup is ffdhe8192 => 1024 byte shared secret is largest secret that can be generated
#define SCOSSL_DH_SHARED_SECRET_MAX_LEN (1024)

void TestDhDlgroup(const BIGNUM* p)
{
    DH* key1 = NULL;
    DH* key2 = NULL;
    unsigned char sharedSecretBytes1[SCOSSL_DH_SHARED_SECRET_MAX_LEN] = { 0 };
    unsigned char sharedSecretBytes2[SCOSSL_DH_SHARED_SECRET_MAX_LEN] = { 0 };
    BIGNUM* p1 = NULL;
    BIGNUM* g1 = NULL;
    BIGNUM* p2 = NULL;
    BIGNUM* g2 = NULL;
    const BIGNUM* cp1 = NULL;
    const BIGNUM* cg1 = NULL;
    const BIGNUM* pubkey1 = NULL;
    const BIGNUM* pubkey2 = NULL;

    printf("Command DH_new\n");
    key1 = DH_new();
    key2 = DH_new();
    if( key1 == NULL || key2 == NULL )
    {
        handleOpenSSLError("DH_new failed\n");
        goto end;
    }

    if( p )
    {
        p1 = BN_dup(p);
        p2 = BN_dup(p);
        g1 = BN_new();
        g2 = BN_new();
        if( p1==NULL || p2==NULL || g1==NULL || g2==NULL || !BN_set_word(g1, 2) || !BN_set_word(g2, 2) )
        {
            handleOpenSSLError("BN_dup, BN_new, or BN_set_word failed\n");
            goto end;
        }

        printf("Command DH_set0_pqg\n");
        if( DH_set0_pqg(key1, p1, NULL, g1) != 1 )
        {
            handleOpenSSLError("DH_set0_pqg failed\n");
            goto end;
        }
        p1 = NULL; // key1 manages p1 and g1 BIGNUMs now
        g1 = NULL;
    } else {
        if( DH_generate_parameters_ex(key1, 1024, 3, NULL) != 1 )
        {
            handleOpenSSLError("DH_generate_parameters_ex failed\n");
            goto end;
        }

        DH_get0_pqg(key1, &cp1, NULL, &cg1);
        p2 = BN_dup(cp1);
        g2 = BN_dup(cg1);
    }

    if( DH_set0_pqg(key2, p2, NULL, g2) != 1 )
    {
        handleOpenSSLError("DH_set0_pqg failed\n");
        goto end;
    }
    p2 = NULL; // key2 manages p2 and g2 BIGNUMs now
    g2 = NULL;

    printf("Command DH_generate_key\n");
    if( DH_generate_key(key1) != 1 || DH_generate_key(key2) != 1 )
    {
        handleOpenSSLError("DH_generate_key failed\n");
        goto end;
    }

    printf("Command DH_get0_key\n");
    DH_get0_key(key1, &pubkey1, NULL);
    DH_get0_key(key2, &pubkey2, NULL);
    if( pubkey1 == NULL || pubkey2 == NULL )
    {
        handleOpenSSLError("DH_get0_key failed\n");
        goto end;
    }

    printf("Command DH_compute_key_padded\n");
    if( DH_compute_key_padded(sharedSecretBytes1, pubkey2, key1) != DH_size(key1) ||
        DH_compute_key_padded(sharedSecretBytes2, pubkey1, key2) != DH_size(key2) )
    {
        handleOpenSSLError("DH_compute_key_padded failed\n");
        goto end;
    }

    if (memcmp(sharedSecretBytes1, sharedSecretBytes2, SCOSSL_DH_SHARED_SECRET_MAX_LEN) != 0)
    {
        handleError("Shared secrets don't match\n");
        goto end;
    }
    else
    {
        printf("ECDH_compute_key Succeeded\n");
    }

end:
    BN_free(p1);
    BN_free(p2);
    BN_free(g1);
    BN_free(g2);
    DH_free(key1);
    DH_free(key2);
    return;
}

void TestDh()
{
    BIGNUM* p_modp = NULL;
    DH* dh_ffdhe = NULL;
    const BIGNUM* p_ffdhe = NULL;

    // Test MODP safe-prime groups
    p_modp = BN_get_rfc3526_prime_2048(NULL);
    TestDhDlgroup(p_modp);
    BN_free(p_modp);
    p_modp = BN_get_rfc3526_prime_3072(NULL);
    TestDhDlgroup(p_modp);
    BN_free(p_modp);
    p_modp = BN_get_rfc3526_prime_4096(NULL);
    TestDhDlgroup(p_modp);
    BN_free(p_modp);

    // Test ffdhe safe-prime groups
    dh_ffdhe = DH_new_by_nid(NID_ffdhe2048);
    DH_get0_pqg(dh_ffdhe, &p_ffdhe, NULL, NULL);
    TestDhDlgroup(p_ffdhe);
    DH_free(dh_ffdhe);
    dh_ffdhe = DH_new_by_nid(NID_ffdhe3072);
    DH_get0_pqg(dh_ffdhe, &p_ffdhe, NULL, NULL);
    TestDhDlgroup(p_ffdhe);
    DH_free(dh_ffdhe);
    dh_ffdhe = DH_new_by_nid(NID_ffdhe4096);
    DH_get0_pqg(dh_ffdhe, &p_ffdhe, NULL, NULL);
    TestDhDlgroup(p_ffdhe);
    DH_free(dh_ffdhe);
    dh_ffdhe = DH_new_by_nid(NID_ffdhe6144);
    DH_get0_pqg(dh_ffdhe, &p_ffdhe, NULL, NULL);
    TestDhDlgroup(p_ffdhe);
    DH_free(dh_ffdhe);
    dh_ffdhe = DH_new_by_nid(NID_ffdhe8192);
    DH_get0_pqg(dh_ffdhe, &p_ffdhe, NULL, NULL);
    TestDhDlgroup(p_ffdhe);
    DH_free(dh_ffdhe);
    // Test an unsupported DH group
    TestDhDlgroup(NULL);

    printf("%s", SeparatorLine);
}

/*
 * Read whole contents of a BIO into an allocated memory buffer and return
 * it.
 */
int bio_to_mem(unsigned char **out, int maxlen, BIO *in)
{
    BIO *mem;
    int len, ret;
    unsigned char tbuf[1024];

    mem = BIO_new(BIO_s_mem());
    if (mem == NULL)
        return -1;
    for (;;) {
        if ((maxlen != -1) && maxlen < 1024)
            len = maxlen;
        else
            len = 1024;
        len = BIO_read(in, tbuf, len);
        if (len < 0) {
            BIO_free(mem);
            return -1;
        }
        if (len == 0)
            break;
        if (BIO_write(mem, tbuf, len) != len) {
            BIO_free(mem);
            return -1;
        }
        maxlen -= len;

        if (maxlen == 0)
            break;
    }
    ret = BIO_get_mem_data(mem, (char **)out);
    BIO_set_flags(mem, BIO_FLAGS_MEM_RDONLY);
    BIO_free(mem);
    return ret;
}

void TestRsaEncryptDecrypt(
        EVP_PKEY *encryptionKey,
        EVP_PKEY *decryptionKey,
        const char* paddingStr,
        int padding)
{
    printf("\nTesting EVP_PKEY Encrypt/Decrypt Functions: Padding: %s(%d)\n", paddingStr, padding);
    unsigned char plaintext[512];
    size_t plaintext_len = 0;

    EVP_PKEY_CTX *pEncryptContext = NULL;
    unsigned char *encryptedtext = NULL;
    size_t encryptedtext_len = 0;

    EVP_PKEY_CTX *pDecryptContext = NULL;
    unsigned char *decryptedtext = NULL;
    size_t decryptedtext_len = 0;

    if (padding == RSA_NO_PADDING) {
        // PlainText has to be size of modulus of the key
        plaintext_len = EVP_PKEY_size(encryptionKey);
    } else {
        plaintext_len = 42; // Choosen at whim
    }

    while(!RAND_bytes(plaintext, plaintext_len));

    if (padding == RSA_NO_PADDING) {
        // PlainText value has to be less than RSA public modulus
        // We can just mask out the most significant bit
        plaintext[0] &= 0xff >> (8 - ((EVP_PKEY_bits(encryptionKey) - 1) & 7));
    }

    if (EVP_PKEY_bits(encryptionKey) <= 512 &&
        padding == RSA_PKCS1_OAEP_PADDING)
    {
        printf("Skipping test: RSA key size ≤ 512 bits is not supported with OAEP padding\n");
        goto end;
    }

    //
    // Encrypt
    //
    printf("\nTesting EVP_PKEY_encrypt* Functions\n\n");
    printf("Command EVP_PKEY_CTX_new\n");
    pEncryptContext = EVP_PKEY_CTX_new(encryptionKey, NULL);
    if (pEncryptContext == NULL)
    {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_PKEY_encrypt_init\n");
    if (EVP_PKEY_encrypt_init(pEncryptContext) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_PKEY_CTX_set_rsa_padding\n");
    if (EVP_PKEY_CTX_set_rsa_padding(pEncryptContext, padding) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    /* Determine buffer length */
    printf("Command EVP_PKEY_encrypt\n");
    if (EVP_PKEY_encrypt(
            pEncryptContext,
            NULL,
            &encryptedtext_len,
            plaintext,
            plaintext_len) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    encryptedtext = (unsigned char *)OPENSSL_zalloc(encryptedtext_len);
    printf("Command EVP_PKEY_encrypt (%zu)\n", encryptedtext_len);
    if (EVP_PKEY_encrypt(
            pEncryptContext,
            encryptedtext,
            &encryptedtext_len,
            plaintext,
            plaintext_len) <= 0) {
        handleOpenSSLError("");
        goto end;
    }

    printf("PlainText:\n");
    BIO_dump_fp (stdout, (const char *)plaintext, plaintext_len);
    printf("EncryptedText:\n");
    BIO_dump_fp (stdout, (const char *)encryptedtext, encryptedtext_len);

    //
    // Decrypt with Private Key
    //

    printf("\nTesting EVP_PKEY_decrypt* Functions\n\n");

    printf("Command EVP_PKEY_encrypt\n");
    pDecryptContext = EVP_PKEY_CTX_new(decryptionKey, NULL);
    if (pDecryptContext == NULL) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_PKEY_decrypt_init\n");
    if (EVP_PKEY_decrypt_init(pDecryptContext) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_PKEY_CTX_set_rsa_padding\n");
    if (EVP_PKEY_CTX_set_rsa_padding(pDecryptContext, padding) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    /* Determine buffer length */
    printf("Command EVP_PKEY_decrypt\n");
    if (EVP_PKEY_decrypt(pDecryptContext, NULL, &decryptedtext_len, (const unsigned char*)encryptedtext, encryptedtext_len) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    decryptedtext = (unsigned char *)OPENSSL_zalloc(decryptedtext_len);
    printf("Command EVP_PKEY_decrypt\n");
    if (EVP_PKEY_decrypt(pDecryptContext, decryptedtext, &decryptedtext_len, (const unsigned char*)encryptedtext, encryptedtext_len) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    printf("DecryptedText:\n");
    BIO_dump_fp (stdout, (const char *)decryptedtext, decryptedtext_len);

    if (decryptedtext_len != plaintext_len ||
        memcmp(plaintext, decryptedtext, decryptedtext_len) != 0)
    {
        handleError("PlainText and DecryptedText don't match\n");
        goto end;
    }
    else
    {
        printf("PlainText and DecryptedText match\n");
    }

end:
    if (encryptedtext)
        OPENSSL_free(encryptedtext);
    if (decryptedtext)
        OPENSSL_free(decryptedtext);
    if (pEncryptContext)
        EVP_PKEY_CTX_free(pEncryptContext);
    if (pDecryptContext)
        EVP_PKEY_CTX_free(pDecryptContext);
    printf("%s", SeparatorLine);
    return;
}

void TestRsaSignVerify(
        EVP_PKEY *signingKey,
        EVP_PKEY *verificationKey,
        const char* paddingStr,
        int padding,
        int saltlen,
        const char* digestStr,
        const EVP_MD *digest,
        size_t digest_length
        )
{
    printf("\nTesting EVP_PKEY Sign/Verify Functions: Padding: %s(%d), digest: %s\n", paddingStr, padding, digestStr);
    EVP_PKEY_CTX *pSignContext = NULL;
    unsigned char *signature = NULL;
    size_t signature_len = 0;
    EVP_PKEY_CTX *pVerifyContext = NULL;
    unsigned char message_digest[64];
    size_t message_digest_len = digest_length;
    int ret = 0;

    if (EVP_PKEY_bits(signingKey) <= 512 &&
        EVP_MD_size(digest) >= 32)
    {
        printf("Skipping test: key size ≤ 512 bits is not supported with SHA-256 or stronger digests\n");
        goto end;
    }

    while(!RAND_bytes(message_digest, digest_length));
    printf("\nTesting EVP_PKEY_sign* Functions - PKCS1 PADDING\n\n");
    printf("Command EVP_PKEY_CTX_new\n");
    pSignContext = EVP_PKEY_CTX_new(signingKey, NULL);
    if (pSignContext == NULL) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_PKEY_sign_init\n");
    if (EVP_PKEY_sign_init(pSignContext) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_PKEY_CTX_set_rsa_padding\n");
    if (EVP_PKEY_CTX_set_rsa_padding(pSignContext, padding) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_PKEY_CTX_set_signature_md\n");
    if (EVP_PKEY_CTX_set_signature_md(pSignContext, digest) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    if (padding == RSA_PKCS1_PSS_PADDING)
    {
        printf("Command EVP_PKEY_CTX_set_rsa_pss_saltlen (%d)\n", saltlen);
        if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pSignContext, saltlen) <= 0)
        {
            handleOpenSSLError("");
            goto end;
        }
    }
    /* Determine buffer length */
    printf("Command EVP_PKEY_sign\n");
    if (EVP_PKEY_sign(pSignContext, NULL, &signature_len, message_digest, message_digest_len) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    signature = (unsigned char *)OPENSSL_zalloc(signature_len);
    if (!signature) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_PKEY_sign\n");
    if (EVP_PKEY_sign(pSignContext, signature, &signature_len, message_digest, message_digest_len) <= 0) {
        handleOpenSSLError("");
        goto end;
    }

    printf("Message Digest:\n");
    BIO_dump_fp (stdout, (const char *)message_digest, message_digest_len);

    printf("Signature:\n");
    BIO_dump_fp (stdout, (const char *)signature, signature_len);

    //
    // Verify with Public Key
    //

    printf("\nTesting EVP_PKEY_verify* Functions\n\n");

    printf("Command EVP_PKEY_sign\n");
    pVerifyContext = EVP_PKEY_CTX_new(verificationKey, NULL);
    if (pVerifyContext == NULL) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_PKEY_verify_init\n");
    if (EVP_PKEY_verify_init(pVerifyContext) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_PKEY_CTX_set_rsa_padding\n");
    if (EVP_PKEY_CTX_set_rsa_padding(pVerifyContext, padding) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_PKEY_CTX_set_signature_md\n");
    if (EVP_PKEY_CTX_set_signature_md(pVerifyContext, digest) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    if (padding == RSA_PKCS1_PSS_PADDING)
    {
        printf("Command EVP_PKEY_CTX_set_rsa_pss_saltlen (%d)\n", saltlen);
        if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pVerifyContext, saltlen) <= 0)
        {
            handleOpenSSLError("");
            goto end;
        }
    }
    printf("Command EVP_PKEY_verify\n");
    ret = EVP_PKEY_verify(pVerifyContext, signature, signature_len, message_digest, message_digest_len);
    if (ret != 1)
    {
        handleError("EVP_PKEY_verify failed\n");
        goto end;
    } else {
        printf("EVP_PKEY_verify succeeded\n");
    }

end:
    if (pSignContext)
        EVP_PKEY_CTX_free(pSignContext);
    if (pVerifyContext)
        EVP_PKEY_CTX_free(pVerifyContext);
    if (signature)
        OPENSSL_free(signature);
    printf("%s", SeparatorLine);
    return;
}

void TestRsaDigestSignVerify(
        EVP_PKEY *signingKey,
        EVP_PKEY *verificationKey,
        const char* paddingStr,
        int padding,
        int saltlen,
        const char* digestStr,
        const EVP_MD *digest
        )
{
    printf("\nTesting EVP_PKEY DigestSign/DigestVerify Functions: digest: %s\n", digestStr);
    EVP_MD_CTX* RSASignCtx = NULL;
    EVP_MD_CTX* RSAVerifyCtx = NULL;
    unsigned char *signature = NULL;
    size_t signature_len = 0;
    const unsigned char plaintext[] =
        "Message for testing EVP_PKEY_Encrypt* APIs for encryption/decryption";
    const char message[] = "My EVP_DigestSign Message";
    size_t message_len = sizeof(message);
    bool authentic = false;
    int AuthStatus = 0;
    EVP_PKEY_CTX *pSigningKeyContext = NULL;
    EVP_PKEY_CTX *pVerificationKeyContext = NULL;

    if (EVP_PKEY_bits(signingKey) <= 512 &&
        EVP_MD_size(digest) >= 32)
    {
        printf("Skipping test: key size ≤ 512 bits is not supported with SHA-256 or stronger digests\n");
        goto end;
    }

    printf("\nTesting DigestSign* Functions\n\n");

    printf("Command EVP_MD_CTX_new\n");
    RSASignCtx = EVP_MD_CTX_new();
    printf("Command EVP_DigestSignInit\n");
    if (EVP_DigestSignInit(RSASignCtx,&pSigningKeyContext, digest, NULL, signingKey)<=0) {
        handleOpenSSLError("");
        goto end;
    }

    if (paddingStr) {
        printf("Setting Padding: %s(%d)\n", paddingStr, padding);
        printf("Command EVP_PKEY_CTX_set_rsa_padding\n");
        if (EVP_PKEY_CTX_set_rsa_padding(pSigningKeyContext, padding)<=0) {
            handleOpenSSLError("");
            goto end;
        }
        // if (EVP_PKEY_CTX_set_rsa_mgf1_md(pSigningKeyContext, EVP_sha512())<=0) {
        //     printOpenSSLError("");
        //     goto end;
        // }
    }
    if (padding == RSA_PKCS1_PSS_PADDING)
    {
        printf("Command EVP_PKEY_CTX_set_rsa_pss_saltlen (%d)\n", saltlen);
        if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pSigningKeyContext, saltlen) <= 0)
        {
            handleOpenSSLError("");
            goto end;
        }
    }

    printf("Command EVP_DigestSignUpdate\n");
    if (EVP_DigestSignUpdate(RSASignCtx, message, message_len) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_DigestSignFinal\n");
    if (EVP_DigestSignFinal(RSASignCtx, NULL, &signature_len) <=0) {
        handleOpenSSLError("");
        goto end;
    }
    printf("signature_length= %ld\n", signature_len);
    signature = (unsigned char*)OPENSSL_zalloc(signature_len);
    printf("Command EVP_DigestSignFinal\n");
    if (EVP_DigestSignFinal(RSASignCtx, signature, &signature_len) <= 0) {
        handleOpenSSLError("");
        goto end;
    }

    printf("Message:\n");
    BIO_dump_fp (stdout, (const char *)message, message_len);

    printf("Signature:\n");
    BIO_dump_fp (stdout, (const char *)signature, signature_len);

    //
    // DigestVerify
    //
    printf("\nTesting EVP_DigestVerify* Functions\n\n");
    printf("Command EVP_MD_CTX_new\n");
    RSAVerifyCtx = EVP_MD_CTX_new();
    printf("Verify Signature\n");
    printf("Command EVP_DigestVerifyInit\n");
    if (EVP_DigestVerifyInit(RSAVerifyCtx,&pVerificationKeyContext, digest,NULL,verificationKey)<=0) {
        handleOpenSSLError("");
        goto end;
    }
    if (paddingStr) {
        printf("Setting Padding: %s(%d)\n", paddingStr, padding);
        printf("Command EVP_PKEY_CTX_set_rsa_padding\n");
        if (EVP_PKEY_CTX_set_rsa_padding(pVerificationKeyContext, padding)<=0) {
            handleOpenSSLError("");
            goto end;
        }
        // if (EVP_PKEY_CTX_set_rsa_mgf1_md(pVerificationKeyContext, EVP_sha512())<=0) {
        //     printOpenSSLError("");
        //     goto end;
        // }
    }
    if (padding == RSA_PKCS1_PSS_PADDING)
    {
        printf("Command EVP_PKEY_CTX_set_rsa_pss_saltlen (%d)\n", saltlen);
        if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pVerificationKeyContext, saltlen) <= 0)
        {
            handleOpenSSLError("");
            goto end;
        }
    }
    printf("Command EVP_DigestVerifyUpdate\n");
    if (EVP_DigestVerifyUpdate(RSAVerifyCtx, message, message_len) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_DigestVerifyFinal\n");
    AuthStatus = EVP_DigestVerifyFinal(RSAVerifyCtx, signature, signature_len);
    if (AuthStatus==1) {
        authentic = true;
    } else if(AuthStatus==0) {
        authentic = false;
    } else {
        authentic = false;
    }
    if (!authentic) {
        handleError("Signature Not Verified\n");
    } else {
        printf("Signature Verified\n");
    }

end:
    if (RSASignCtx)
        EVP_MD_CTX_free(RSASignCtx);
    if (RSAVerifyCtx)
        EVP_MD_CTX_free(RSAVerifyCtx);
    if (signature)
        OPENSSL_free(signature);
    return;
}

void TestRsaSealOpen(
        EVP_PKEY *sealKey,
        EVP_PKEY *openKey,
        const char* cipherStr,
        const EVP_CIPHER *cipher
        )
{
    printf("\nTesting EVP_PKEY Seal/Open Functions: Cipher: %s\n", cipherStr);
    EVP_CIPHER_CTX *rsaSealCtx = NULL;
    EVP_CIPHER_CTX *rsaOpenCtx = NULL;
    static const unsigned char message[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
    size_t message_len = sizeof(message);
    unsigned char *encKey = NULL;
    int encKey_len = 0;
    unsigned char iv[EVP_MAX_IV_LENGTH];
    unsigned char ciphertext[32], plaintext[16];
    int ciphertext_len = 0;
    int encryptedBlockLen = 0;

    int decryptedMessageLen = 0;
    int decryptedBlockLen = 0;
    unsigned char *decryptedMessage = NULL;

    memset(iv, 0, EVP_MAX_IV_LENGTH);
    memset(ciphertext, 0, 32);
    memset(plaintext, 0, 16);

    printf("\nTesting EVP_Seal* Functions\n\n");

    printf("Command EVP_CIPHER_CTX_new\n");
    rsaSealCtx = EVP_CIPHER_CTX_new();

    printf("Command EVP_CIPHER_CTX_init\n");
    if (EVP_CIPHER_CTX_init(rsaSealCtx) != 1) {
        handleOpenSSLError("");
        goto end;
    }
    encKey = (unsigned char *) OPENSSL_zalloc(EVP_PKEY_size(sealKey));
    printf("Command EVP_SealInit\n");
    if (EVP_SealInit(rsaSealCtx, cipher, &encKey, &encKey_len, iv, &sealKey, 1) != 1) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_SealUpdate\n");
    if (EVP_SealUpdate(rsaSealCtx, ciphertext, &ciphertext_len, message, message_len) != 1) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_SealFinal\n");
    if (EVP_SealFinal(rsaSealCtx, ciphertext + ciphertext_len, &encryptedBlockLen) != 1) {
        handleOpenSSLError("");
        goto end;
    }
    ciphertext_len += encryptedBlockLen;

    printf("Message:\n");
    BIO_dump_fp (stdout, (const char *)message, message_len);

    printf("Sealed output:\n");
    BIO_dump_fp (stdout, (const char *)ciphertext, ciphertext_len);

    //
    // Open
    //

    printf("\nTesting EVP_Open* Functions\n\n");
    printf("Command EVP_CIPHER_CTX_new\n");
    rsaOpenCtx = EVP_CIPHER_CTX_new();

    printf("Command EVP_CIPHER_CTX_init\n");
    if (EVP_CIPHER_CTX_init(rsaOpenCtx) != 1) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_OpenInit\n");
    if (EVP_OpenInit(rsaOpenCtx, cipher, encKey, encKey_len, iv, openKey) != 1) {
        handleOpenSSLError("");
        goto end;
    }
    decryptedMessage = (unsigned char *) OPENSSL_zalloc(ciphertext_len + EVP_MAX_IV_LENGTH);
    // the length of the encrypted message
    decryptedMessageLen = 0;
    decryptedBlockLen = 0;
    // decrypt message with AES secret
    printf("Command EVP_OpenUpdate\n");
    if (EVP_OpenUpdate(rsaOpenCtx, decryptedMessage, &decryptedMessageLen, ciphertext, ciphertext_len) != 1) {
        handleOpenSSLError("");
        goto end;
    }
    // finalize by decrypting padding
    printf("Command EVP_OpenFinal\n");
    EVP_OpenFinal(rsaOpenCtx, decryptedMessage + decryptedMessageLen, &decryptedBlockLen);
    decryptedMessageLen += decryptedBlockLen;

    printf("Opened Bytes:\n");
    BIO_dump_fp (stdout, (const char *)decryptedMessage, decryptedMessageLen);

    if (message_len != decryptedMessageLen ||
        memcmp(message,decryptedMessage, decryptedMessageLen) != 0)
    {
        handleError("Decrypted/Opened text don't match original message\n");
    }
    else
    {
        printf("Decrypted/Opened text match original message\n");
    }

end:
    if (encKey)
        OPENSSL_free(encKey);
    if (rsaSealCtx)
        EVP_CIPHER_CTX_free(rsaSealCtx);
    if (rsaOpenCtx)
        EVP_CIPHER_CTX_free(rsaOpenCtx);
    if (decryptedMessage)
        OPENSSL_free(decryptedMessage);
    printf("%s", SeparatorLine);
    return;
}

int CreateKeys(int id, int modulus, uint32_t exponent, char* publicFileName, char* privateFileName, EVP_PKEY** publicKey, EVP_PKEY** privateKey)
{
    BIGNUM* exponent_bn = NULL;
    EVP_PKEY* pKey = NULL;
    EVP_PKEY_CTX* pKeyContext = NULL;
    BIO *privateBIO = NULL;
    BIO *publicBIO = NULL;
    FILE *fp = NULL;
    int ret = -1;

    //
    // Generate RSA Key
    //
    printf("Command EVP_PKEY_CTX_new_id\n");
    pKeyContext = EVP_PKEY_CTX_new_id(id, NULL);
    if (pKeyContext == NULL) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_PKEY_keygen_init\n");
    if (EVP_PKEY_keygen_init(pKeyContext) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_PKEY_CTX_set_rsa_keygen_bits\n");
    if (EVP_PKEY_CTX_set_rsa_keygen_bits(pKeyContext, modulus) <= 0) {
        handleOpenSSLError("");
        goto end;
    }
    exponent_bn = BN_new();
    BN_set_word(exponent_bn, exponent);

    // API name change. Note, that in 3.0 it is now the caller's
    // responsibility to free exponent_bn
#if OPENSSL_VERSION_MAJOR == 3
    if (EVP_PKEY_CTX_set1_rsa_keygen_pubexp(pKeyContext, exponent_bn) <= 0) {
#else
    if (EVP_PKEY_CTX_set_rsa_keygen_pubexp(pKeyContext, exponent_bn) <= 0) {
#endif
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_PKEY_keygen\n");
    if (EVP_PKEY_keygen(pKeyContext, &pKey) != 1) {
        handleOpenSSLError("");
        goto end;
    }

    printf("%s", SeparatorLine);

    //
    // Export Public Key
    //
    printf("\nTesting Exporting Public Key\n\n");
    publicBIO = BIO_new(BIO_s_mem());
    PEM_write_bio_PUBKEY(publicBIO, pKey);
    fp = fopen(publicFileName, "w");
    PEM_write_PUBKEY(fp, pKey);
    PEM_write_PUBKEY(stdout, pKey);
    fflush(fp);
    fclose(fp);
    BIO_free(publicBIO);
    printf("%s", SeparatorLine);

    //
    // Export Private Key
    //
    printf("\nTesting Exporting Private Key\n\n");
    privateBIO = BIO_new(BIO_s_mem());
    PEM_write_bio_PrivateKey(privateBIO, pKey, NULL, NULL, 0, 0, NULL);
    fp = fopen(privateFileName, "w");
    PEM_write_PrivateKey(fp, pKey, NULL, NULL, 0, NULL, NULL);
    PEM_write_PrivateKey(stdout, pKey, NULL, NULL, 0, NULL, NULL);
    fflush(fp);
    fclose(fp);
    BIO_free(privateBIO);
    printf("%s", SeparatorLine);

    //
    // Import Public Key
    //
    printf("\nTesting Importing Public Key\n\n");
    fp = fopen(publicFileName, "r");
    *publicKey = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
    fflush(fp);
    fclose(fp);
    printf("%s", SeparatorLine);

    //
    // Import Private Key
    //
    printf("\nTesting Importing Private Key\n\n");
    fp = fopen(privateFileName, "r");
    *privateKey = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
    fflush(fp);
    fclose(fp);
    printf("%s", SeparatorLine);

    ret = 0;

end:
    if (pKeyContext)
        EVP_PKEY_CTX_free(pKeyContext);
    if (pKey)
        EVP_PKEY_free(pKey);

    // In 1.1.1, the EVP_PKEY_CTX takes ownership of this bignum and will free it
    // in EVP_PKEY_CTX_free. In 3.0, it's the caller's responsibility.
#if OPENSSL_VERSION_MAJOR == 3
    BN_free(exponent_bn);
#endif
    return ret;
}

void TestRsaEvp(int modulus, uint32_t exponent)
{
    printf("\nTest RSA: Modulus: %d, Exponent: %d\n\n", modulus, exponent);
    EVP_PKEY *privateKey = NULL;
    EVP_PKEY *privateKeyPss = NULL;
    EVP_PKEY *publicKey = NULL;
    EVP_PKEY *publicKeyPss = NULL;
    char publicFileName[1024];
    char publicPssFileName[1024];
    char privateFileName[1024];
    char privatePssFileName[1024];

    // Initialize Data
    sprintf(publicFileName, "%s_%d.pem", "public",  modulus);
    sprintf(privateFileName, "%s_%d.pem", "private",  modulus);
    sprintf(publicPssFileName, "%s_pss_%d.pem", "public",  modulus);
    sprintf(privatePssFileName, "%s_pss_%d.pem", "private",  modulus);

    printf("\nTesting EVP_PKEY_keygen* Functions\n\n");
    if( CreateKeys(EVP_PKEY_RSA, modulus, exponent, publicFileName, privateFileName, &publicKey, &privateKey) == -1 )
    {
        handleError("CreateKeys EVP_PKEY_RSA failed");
        goto end;
    }
    if( CreateKeys(EVP_PKEY_RSA_PSS, modulus, exponent, publicPssFileName, privatePssFileName, &publicKeyPss, &privateKeyPss) == -1 )
    {
        handleError("CreateKeys EVP_PKEY_RSA_PSS failed");
        goto end;
    }

    //
    // Encrypt/Decrypt
    //
    TestRsaEncryptDecrypt(publicKey, privateKey, "RSA_PKCS1_PADDING", RSA_PKCS1_PADDING);
    TestRsaEncryptDecrypt(publicKey, privateKey, "RSA_PKCS1_OAEP_PADDING", RSA_PKCS1_OAEP_PADDING);
    // SSLV23 fails with sslv3 rollback attack in OpenSSL 1.1.1n-dev
    // TestRsaEncryptDecrypt(publicKey, privateKey, "RSA_SSLV23_PADDING", RSA_SSLV23_PADDING);
    TestRsaEncryptDecrypt(publicKey, privateKey, "RSA_NO_PADDING", RSA_NO_PADDING);
    printf("%s", SeparatorLine);

    //
    // Sign/Verify
    //

    // MD5+PKCS1 is not supported by the provider
#if OPENSSL_VERSION_MAJOR == 1
    // Test engine fallback
    TestRsaSignVerify(privateKey, publicKey, "RSA_PKCS1_NO_IMPLICIT_REJECT_PADDING", RSA_PKCS1_NO_IMPLICIT_REJECT_PADDING, 0, "EVP_sha256", EVP_sha256(), 32);
    printf("%s", SeparatorLine);

    TestRsaSignVerify(privateKey, publicKey, "RSA_PKCS1_PADDING", RSA_PKCS1_PADDING, 0, "EVP_MD5", EVP_md5(), 16);
#endif
    TestRsaSignVerify(privateKey, publicKey, "RSA_PKCS1_PADDING", RSA_PKCS1_PADDING, 0, "EVP_sha1", EVP_sha1(), 20);
    TestRsaSignVerify(privateKey, publicKey, "RSA_PKCS1_PADDING", RSA_PKCS1_PADDING, 0, "EVP_sha256", EVP_sha256(), 32);
    TestRsaSignVerify(privateKey, publicKey, "RSA_PKCS1_PADDING", RSA_PKCS1_PADDING, 0, "EVP_sha384", EVP_sha384(), 48);
    TestRsaSignVerify(privateKey, publicKey, "RSA_PKCS1_PADDING", RSA_PKCS1_PADDING, 0, "EVP_sha512", EVP_sha512(), 64);
    printf("%s", SeparatorLine);

    TestRsaSignVerify(privateKey, publicKey, "RSA_PKCS1_PSS_PADDING", RSA_PKCS1_PSS_PADDING, RSA_PSS_SALTLEN_DIGEST, "EVP_sha256", EVP_sha256(), 32);
    TestRsaSignVerify(privateKey, publicKey, "RSA_PKCS1_PSS_PADDING", RSA_PKCS1_PSS_PADDING, RSA_PSS_SALTLEN_MAX, "EVP_sha256", EVP_sha256(), 32);
    TestRsaSignVerify(privateKeyPss, publicKeyPss, "RSA_PKCS1_PSS_PADDING", RSA_PKCS1_PSS_PADDING, RSA_PSS_SALTLEN_DIGEST, "EVP_sha256", EVP_sha256(), 32);
    TestRsaSignVerify(privateKeyPss, publicKeyPss, "RSA_PKCS1_PSS_PADDING", RSA_PKCS1_PSS_PADDING, RSA_PSS_SALTLEN_MAX, "EVP_sha256", EVP_sha256(), 32);
    printf("%s", SeparatorLine);

    //
    // DigestSign/DigestVerify
    //

    // MD5+PKCS1 is not supported by the provider
#if OPENSSL_VERSION_MAJOR == 1
    TestRsaDigestSignVerify(privateKey, publicKey, "RSA_PKCS1_PADDING", RSA_PKCS1_PADDING, 0, "EVP_MD5", EVP_md5());
#endif
    TestRsaDigestSignVerify(privateKey, publicKey, "RSA_PKCS1_PADDING", RSA_PKCS1_PADDING, 0, "EVP_sha1", EVP_sha1());
    TestRsaDigestSignVerify(privateKey, publicKey, "RSA_PKCS1_PADDING", RSA_PKCS1_PADDING, 0, "EVP_sha256", EVP_sha256());
    TestRsaDigestSignVerify(privateKey, publicKey, "RSA_PKCS1_PADDING", RSA_PKCS1_PADDING, 0, "EVP_sha384", EVP_sha384());
    TestRsaDigestSignVerify(privateKey, publicKey, "RSA_PKCS1_PADDING", RSA_PKCS1_PADDING, 0, "EVP_sha512", EVP_sha512());
    printf("%s", SeparatorLine);

    TestRsaDigestSignVerify(privateKey, publicKey, "RSA_PKCS1_PSS_PADDING", RSA_PKCS1_PSS_PADDING, RSA_PSS_SALTLEN_DIGEST, "EVP_sha256", EVP_sha256());
    TestRsaDigestSignVerify(privateKey, publicKey, "RSA_PKCS1_PSS_PADDING", RSA_PKCS1_PSS_PADDING, RSA_PSS_SALTLEN_MAX, "EVP_sha256", EVP_sha256());
    TestRsaDigestSignVerify(privateKeyPss, publicKeyPss, "RSA_PKCS1_PSS_PADDING", RSA_PKCS1_PSS_PADDING, RSA_PSS_SALTLEN_DIGEST, "EVP_sha256", EVP_sha256());
    TestRsaDigestSignVerify(privateKeyPss, publicKeyPss, "RSA_PKCS1_PSS_PADDING", RSA_PKCS1_PSS_PADDING, RSA_PSS_SALTLEN_MAX, "EVP_sha256", EVP_sha256());
    printf("%s", SeparatorLine);

    //
    // Seal/Open
    //
    TestRsaSealOpen(publicKey, privateKey, "EVP_aes_128_cbc", EVP_aes_128_cbc());
    TestRsaSealOpen(publicKey, privateKey, "EVP_aes_192_cbc", EVP_aes_192_cbc());
    TestRsaSealOpen(publicKey, privateKey, "EVP_aes_256_cbc", EVP_aes_256_cbc());
    TestRsaSealOpen(publicKey, privateKey, "EVP_aes_128_ecb", EVP_aes_128_ecb());
    TestRsaSealOpen(publicKey, privateKey, "EVP_aes_192_ecb", EVP_aes_192_ecb());
    TestRsaSealOpen(publicKey, privateKey, "EVP_aes_256_ecb", EVP_aes_256_ecb());
    printf("%s", SeparatorLine);

    printf("\nCompleted Test RSA: Modulus: %d, Exponent: %d\n\n", modulus, exponent);
    printf("%s", SeparatorLine);

end:

    if (publicKey)
        EVP_PKEY_free(publicKey);
    if (privateKey)
        EVP_PKEY_free(privateKey);
    if (publicKeyPss)
        EVP_PKEY_free(publicKeyPss);
    if (privateKeyPss)
        EVP_PKEY_free(privateKeyPss);
    printf("%s", SeparatorLine);
    return;
}

void TestRsaEvpAll()
{
    TestRsaEvp(512, 65537);
    TestRsaEvp(1024, 65537);
    TestRsaEvp(2048, 65537);
    TestRsaEvp(2049, 65537);
    TestRsaEvp(3072, 65537);
    TestRsaEvp(4096, 65537);
    printf("%s", SeparatorLine);

}

bool TestDigest(const char* digestname, const char *expected)
{
    bool result = false;
    EVP_MD_CTX *mdctx;
    char mess1[] = "Test Message1234567";
    char mess2[] = "Hello World";
    unsigned char md_value[EVP_MAX_MD_SIZE];
    unsigned int md_len, i;

    printf("\nTestDigest: %s\n\n", digestname);

    const EVP_MD *md = EVP_get_digestbyname(digestname);
    if (md == NULL)
    {
         printf("No Digest found for %s\n", digestname);
        goto end;
    }

    printf("Command EVP_MD_CTX_new\n");
    mdctx = EVP_MD_CTX_new();
    printf("Command EVP_DigestInit_ex\n");
    EVP_DigestInit_ex(mdctx, md, NULL);
    printf("Command EVP_DigestUpdate\n");
    EVP_DigestUpdate(mdctx, mess1, strlen(mess1));
    printf("Command EVP_DigestUpdate\n");
    EVP_DigestUpdate(mdctx, mess2, strlen(mess2));
    printf("Command EVP_DigestFinal_ex\n");
    EVP_DigestFinal_ex(mdctx, md_value, &md_len);
    printf("Command EVP_MD_CTX_free\n");
    EVP_MD_CTX_free(mdctx);

    printf("Digest (%s)  : \t", digestname);
    for (i = 0; i < md_len; i++)
         printf("%02x", md_value[i]);
    printf("\n");

    if (memcmp(md_value, expected, md_len) != 0)
    {
        handleError("Result does not match expected value");
        goto end;
    }

    result = true;
end:
    printf("%s", SeparatorLine);
    return result;
}

#if OPENSSL_VERSION_MAJOR == 3
bool TestDigestImportExport(const char *digestname, const char *expected, size_t export_state_size)
{
    bool result = false;
    EVP_MD *md = NULL;
    EVP_MD_CTX *mdctx;
    char mess1[] = "Test Message1234567";
    char mess2[] = "Hello World";
    unsigned char md_value[EVP_MAX_MD_SIZE];
    unsigned int md_len, i;

    const OSSL_PROVIDER *provider;
    const char *providername;
    const OSSL_PARAM *capableParams = NULL;
    BYTE exported_state[export_state_size];
    int recompute_checksum = 0;
    OSSL_PARAM params[3] = {
        OSSL_PARAM_construct_octet_string("state", exported_state, export_state_size),
        OSSL_PARAM_construct_int("recompute_checksum", &recompute_checksum),
        OSSL_PARAM_construct_end()
    };

    printf("\nTestDigestImportExport: %s\n\n", digestname);

    md = EVP_MD_fetch(nullptr, digestname, "provider=symcryptprovider");
    if (md == NULL)
    {
        printf("No Digest found for %s\n", digestname);
        goto end;
    }

    printf("Command EVP_MD_CTX_new\n");
    mdctx = EVP_MD_CTX_new();
    printf("Command EVP_DigestInit_ex\n");
    EVP_DigestInit_ex2(mdctx, md, NULL);

    // Check capabilities
    capableParams = EVP_MD_CTX_gettable_params(mdctx);
    if (OSSL_PARAM_locate_const(capableParams, "state") == NULL)
    {
        handleError("state parameter not gettable");
        goto end;
    }

    capableParams = EVP_MD_CTX_settable_params(mdctx);
    if (OSSL_PARAM_locate_const(capableParams, "state") == NULL ||
        OSSL_PARAM_locate_const(capableParams, "recompute_checksum") == NULL)
    {
        handleError("state parameter not settable");
        goto end;
    }

    printf("Command EVP_DigestUpdate\n");
    EVP_DigestUpdate(mdctx, mess1, strlen(mess1));

    // Export state
    printf("Command EVP_MD_CTX_get_params\n");
    EVP_MD_CTX_get_params(mdctx, params);
    printf("Command EVP_MD_CTX_free\n");
    EVP_MD_CTX_free(mdctx);

    // New context with imported state supplied
    printf("Command EVP_MD_CTX_new\n");
    mdctx = EVP_MD_CTX_new();
    printf("Command EVP_DigestInit_ex\n");
    EVP_DigestInit_ex2(mdctx, md, params);

    printf("Command EVP_DigestUpdate\n");
    EVP_DigestUpdate(mdctx, mess2, strlen(mess2));
    printf("Command EVP_DigestFinal_ex\n");
    EVP_DigestFinal_ex(mdctx, md_value, &md_len);
    printf("Command EVP_MD_CTX_free\n");
    EVP_MD_CTX_free(mdctx);

    printf("Digest (Original checksum) (%s)  : \t", digestname);
    for (i = 0; i < md_len; i++)
         printf("%02x", md_value[i]);
    printf("\n");

    if (memcmp(md_value, expected, md_len) != 0)
    {
        handleError("Result does not match expected value");
        goto end;
    }

    // Clear last 8 bytes of state (Marvin32 checksum of SymCrypt export)
    OPENSSL_cleanse(exported_state + export_state_size - 8, 8);
    recompute_checksum = 1;

    printf("Command EVP_MD_CTX_new\n");
    mdctx = EVP_MD_CTX_new();
    printf("Command EVP_DigestInit_ex\n");
    EVP_DigestInit_ex2(mdctx, md, params);

    printf("Command EVP_DigestUpdate\n");
    EVP_DigestUpdate(mdctx, mess2, strlen(mess2));
    printf("Command EVP_DigestFinal_ex\n");
    EVP_DigestFinal_ex(mdctx, md_value, &md_len);
    printf("Command EVP_MD_CTX_free\n");
    EVP_MD_CTX_free(mdctx);

    printf("Digest (Recomputed checksum) (%s)  : \t", digestname);
    for (i = 0; i < md_len; i++)
         printf("%02x", md_value[i]);
    printf("\n");

    if (memcmp(md_value, expected, md_len) != 0)
    {
        handleError("Result does not match expected value");
        goto end;
    }

    result = true;

end:
    EVP_MD_free(md);

    printf("%s", SeparatorLine);
    return result;
}
#endif

typedef struct {
    const char *digestname;
    const char *expected;
    size_t export_state_size;
} SCOSSL_DIGEST_TEST_CASE;

// TestDigests tests hash state export and import if supported.
// This functionality is added by the SymCrypt provider and is
// only available when the engine is not loaded. If the engine
// is loaded, OpenSSL will use it for digests and the import/export
// functionality will not be available.
void TestDigests(bool useEngine)
{
    char mess1[] = "Test Message1234567";
    char mess2[] = "Hello World";
    char expected_md_value[EVP_MAX_MD_SIZE];
    unsigned int md_len=32, i, j;
    const char *digestname, *expected;

    const SCOSSL_DIGEST_TEST_CASE digest_test_cases[] =
    {
        {"MD5", "4d1074949b1b41311eff270f84804433",
            SYMCRYPT_MD5_STATE_EXPORT_SIZE},
        {"SHA1", "fc5f088a0395c8ad887ff531333b0405a7dbb098",
            SYMCRYPT_SHA1_STATE_EXPORT_SIZE},
        {"SHA224", "6fe30848035b96665e1f02eae7b2bcafa600e466d80bc1b93b6094cc",
            SYMCRYPT_SHA224_STATE_EXPORT_SIZE},
        {"SHA256", "6810f8a56670662c0e7a10ca415f270eae0aeb1f7b153d02668ae5e7143fcc40",
            SYMCRYPT_SHA256_STATE_EXPORT_SIZE},
        {"SHA384", "1f051f47c1b29006383bbc07f0f14b4cb81bac3c78604e2164e89acddf701264f9b5664686412b17624f67baf3af28a7",
            SYMCRYPT_SHA384_STATE_EXPORT_SIZE},
        {"SHA512", "070820b341fd2a45d28b390e67e3f3e4ce30c5b345540e1f587ccfbe94e22c3079f6c28e906b95871a21d1871a198c581ee9a71b7bcaec5f542d026f5dc87183",
            SYMCRYPT_SHA512_STATE_EXPORT_SIZE},
#if OPENSSL_VERSION_MAJOR == 3
        {"SHA512-224", "ff3da53a61923d460f85f98de06c6312092d0be3712ee611c4fe0a19",
            SYMCRYPT_SHA512_224_STATE_EXPORT_SIZE},
        {"SHA512-256", "036dbd97db1e37aabe6ded8ef9ead203e9adb02ad5596ac5af072dd7374993a0",
            SYMCRYPT_SHA512_256_STATE_EXPORT_SIZE},
        {"SHA3-224", "489a032b8923a05eca5b40f2ed9838f218c65bd082acc48fa2067213",
            SYMCRYPT_SHA3_224_STATE_EXPORT_SIZE},
        {"SHA3-256", "375e793a6d4e4947658e78cb697789434b8279feb2ec9595d03e44473ac478f6",
            SYMCRYPT_SHA3_256_STATE_EXPORT_SIZE},
        {"SHA3-384", "1d47002a9e96d5b6bdd70d476fd2038e50ac3eb0d4202b4eb988f02185fbb9c85cb7ed62804ddaff894e84d62e5832f2",
            SYMCRYPT_SHA3_384_STATE_EXPORT_SIZE},
        {"SHA3-512", "bf63544ae59243a5419a3ff5f598352eb1409d41dc746c9e9d5f258cddaff4e7f7b9d9ae13e90eb07f27e4e157b3fcf796f6554732a2e78a621f7313aba827f3",
            SYMCRYPT_SHA3_512_STATE_EXPORT_SIZE},
#endif
    };

    for (i = 0; i < sizeof(digest_test_cases)/sizeof(digest_test_cases[0]); i++)
    {
        // Convert expected hex string to binary
        for (int j = 0; digest_test_cases[i].expected[j] != '\0' && digest_test_cases[i].expected[j + 1] != '\0'; j += 2)
        {
            expected_md_value[j / 2] = OPENSSL_hexchar2int(digest_test_cases[i].expected[j]) << 4 |
                                       OPENSSL_hexchar2int(digest_test_cases[i].expected[j + 1]);
        }

        TestDigest(digest_test_cases[i].digestname, expected_md_value);

#if OPENSSL_VERSION_MAJOR == 3
        if (!useEngine && digest_test_cases[i].export_state_size > 0)
        {
            TestDigestImportExport(digest_test_cases[i].digestname, expected_md_value, digest_test_cases[i].export_state_size);
        }
#endif
    }

    unsigned char md1[SHA256_DIGEST_LENGTH]; // 32 bytes
    SHA256_CTX context;
    SHA256_Init(&context);
    SHA256_Update(&context, mess1, strlen(mess1));
    SHA256_Update(&context, mess2, strlen(mess2));
    SHA256_Final(md1, &context);
    printf("\nSHA256 Digest using Lower level API's is: ");
    for (i = 0; i < md_len; i++)
         printf("%02x", md1[i]);
    printf("\n");
    printf("%s", SeparatorLine);
    return;
}

int encrypt(const EVP_CIPHER *cipher, unsigned char *plaintext, int plaintext_len, unsigned char *key,
            unsigned char *iv, unsigned char *ciphertext)
{
    EVP_CIPHER_CTX *ctx = NULL;
    int len = 0;
    int ciphertext_len = 0;
    printf("Command EVP_CIPHER_CTX_new\n");
    if(!(ctx = EVP_CIPHER_CTX_new()))
    {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_EncryptInit_ex\n");
    if(!EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv))
    {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_EncryptUpdate\n");
    if(!EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
    {
        handleOpenSSLError("");
        goto end;
    }
    ciphertext_len = len;
    printf("Command EVP_EncryptFinal_ex\n");
    if(!EVP_EncryptFinal_ex(ctx, ciphertext + len, &len))
    {
        handleOpenSSLError("");
        goto end;
    }
    printf("len %d\n", len);
    ciphertext_len += len;
end:
    EVP_CIPHER_CTX_free(ctx);
    printf("%s", SeparatorLine);
    return ciphertext_len;
}

int decrypt(const EVP_CIPHER *cipher, unsigned char *ciphertext, int ciphertext_len, unsigned char *key,
            unsigned char *iv, unsigned char *plaintext)
{
    EVP_CIPHER_CTX *ctx;
    int len;
    int plaintext_len;
    printf("Command EVP_CIPHER_CTX_new\n");
    if(!(ctx = EVP_CIPHER_CTX_new()))
    {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_DecryptInit_ex\n");
    if(!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv))
    {
        handleOpenSSLError("");
        goto end;
    }
    printf("Command EVP_DecryptUpdate\n");
    if(!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
    {
        handleOpenSSLError("");
        goto end;
    }
    plaintext_len = len;
    printf("Command EVP_DecryptFinal_ex\n");
    if(!EVP_DecryptFinal_ex(ctx, plaintext + len, &len))
    {
        handleOpenSSLError("");
        goto end;
    }
    plaintext_len += len;

end:
    EVP_CIPHER_CTX_free(ctx);
    printf("%s", SeparatorLine);
    return plaintext_len;
}

bool TestAesCipher(
    const char* ciphername,
    const EVP_CIPHER *cipher,
    unsigned char *key,
    int key_length,
    unsigned char *iv,
    int iv_length,
    unsigned char* plaintext,
    int plaintext_len)
{
    bool result = false;
    unsigned char ciphertext[8192];
    int ciphertext_len = 0;
    unsigned char decryptedtext[8300];
    int decryptedtext_len = 0;

    printf("\nTestAesCipher: %s\n\n", ciphername);

    printf("Key Bytes:\n");
    BIO_dump_fp (stdout, (const char *)key, key_length);

    printf("IV:\n");
    BIO_dump_fp (stdout, (const char *)iv, iv_length);

    printf("PlainText:\n");
    BIO_dump_fp (stdout, (const char *)plaintext, plaintext_len);

    /* Encrypt the plaintext */
    ciphertext_len = encrypt(cipher, plaintext, plaintext_len, key, iv, ciphertext);

    /* Do something useful with the ciphertext here */
    printf("Ciphertext:\n");
    BIO_dump_fp (stdout, (const char *)ciphertext, ciphertext_len);

    /* Decrypt the ciphertext */
    decryptedtext_len = decrypt(cipher, ciphertext, ciphertext_len, key, iv,
        decryptedtext);

    /* Show the decrypted text */
     printf("DecryptedText:\n");
    BIO_dump_fp (stdout, (const char *)decryptedtext, decryptedtext_len);

    if (decryptedtext_len != plaintext_len ||
        memcmp(plaintext, decryptedtext, decryptedtext_len) != 0)
    {
        handleError("PlainText and DecryptedText don't match\n");
        goto end;
    }
    else
    {
        printf("PlainText and DecryptedText match\n");
    }

    result = true;
end:
    /* Clean up */
    EVP_cleanup();
    printf("%s", SeparatorLine);
    return result;
}

void TestAesCbc()
{
    unsigned char plaintext[8192];
    int plaintext_len = 70;
    unsigned char iv[16];
    unsigned char key[32];

    while(!RAND_bytes(key, 32));
    while(!RAND_bytes(iv, 16));
    while(!RAND_bytes(plaintext, plaintext_len));

    TestAesCipher("EVP_aes_128_cbc", EVP_aes_128_cbc(), key, 16, iv, 16, plaintext, plaintext_len);
    TestAesCipher("EVP_aes_192_cbc", EVP_aes_192_cbc(), key, 24, iv, 16, plaintext, plaintext_len);
    TestAesCipher("EVP_aes_256_cbc", EVP_aes_256_cbc(), key, 32, iv, 16, plaintext, plaintext_len);


    printf("%s", SeparatorLine);
    return;
}

void TestAesEcb()
{
    unsigned char plaintext[8192];
    int plaintext_len = 70;
    unsigned char iv[16];
    unsigned char key[32];

    while(!RAND_bytes(key, 32));
    while(!RAND_bytes(iv, 16));
    while(!RAND_bytes(plaintext, plaintext_len));

    TestAesCipher("EVP_aes_128_ecb", EVP_aes_128_ecb(), key, 16, iv, 16, plaintext, plaintext_len);
    TestAesCipher("EVP_aes_192_ecb", EVP_aes_192_ecb(), key, 24, iv, 16, plaintext, plaintext_len);
    TestAesCipher("EVP_aes_256_ecb", EVP_aes_256_ecb(), key, 32, iv, 16, plaintext, plaintext_len);


    printf("%s", SeparatorLine);
    return;
}

void TestAesXts()
{
    unsigned char plaintext[8192];
    int plaintext_len = 64;
    unsigned char iv[16];
    unsigned char key[64];

    while(!RAND_bytes(key, 64));
    while(!RAND_bytes(iv, 16));
    while(!RAND_bytes(plaintext, plaintext_len));

    TestAesCipher("EVP_aes_128_xts", EVP_aes_128_xts(), key, 32, iv, 16, plaintext, plaintext_len);
    TestAesCipher("EVP_aes_256_xts", EVP_aes_256_xts(), key, 64, iv, 16, plaintext, plaintext_len);

    printf("%s", SeparatorLine);
    return;
}


/* AES-GCM test data from NIST public test vectors */

static unsigned char gcm_key[] = {
    0xee, 0xbc, 0x1f, 0x57, 0x48, 0x7f, 0x51, 0x92, 0x1c, 0x04, 0x65, 0x66, 0x5f, 0x8a, 0xe6, 0xd1,
    0x65, 0x8b, 0xb2, 0x6d, 0xe6, 0xf8, 0xa0, 0x69, 0xa3, 0x52, 0x02, 0x93, 0xa5, 0x72, 0x07, 0x8f
};
static unsigned char gcm_iv[] = {
    0x99, 0xaa, 0x3e, 0x68, 0xed, 0x81, 0x73, 0xa0, 0xee, 0xd0, 0x66, 0x84
};
static unsigned char gcm_pt[] = {
    0xf5, 0x6e, 0x87, 0x05, 0x5b, 0xc3, 0x2d, 0x0e, 0xeb, 0x31, 0xb2, 0xea, 0xcc, 0x2b, 0xf2, 0xa5
};
static unsigned char gcm_aad[] = {
    0x4d, 0x23, 0xc3, 0xce, 0xc3, 0x34, 0xb4, 0x9b, 0xdb, 0x37, 0x0c, 0x43, 0x7f, 0xec, 0x78, 0xde
};
static unsigned char gcm_ct[] = {
    0xf7, 0x26, 0x44, 0x13, 0xa8, 0x4c, 0x0e, 0x7c, 0xd5, 0x36, 0x86, 0x7e, 0xb9, 0xf2, 0x17, 0x36
};
static unsigned char gcm_tag[] = {
    0x67, 0xba, 0x05, 0x10, 0x26, 0x2a, 0xe4, 0x87, 0xd7, 0x37, 0xee, 0x62, 0x98, 0xf7, 0x7e, 0x0c
};

int encrypt_gcm(
    const EVP_CIPHER *cipher, unsigned char *plaintext, int plaintext_len, unsigned char *aad, int aad_len,
    unsigned char *key, unsigned char *iv, unsigned char *ciphertext, unsigned char *tag)
{
    EVP_CIPHER_CTX *ctx;
    int len=0, ciphertext_len=0;
    /* Create and initialise the context */
    if(!(ctx = EVP_CIPHER_CTX_new())) {
        handleOpenSSLError("");
        goto end;
    }
    /* Initialise the encryption operation. */
    if(1 != EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv)) {
        handleOpenSSLError("");
        goto end;
    }
    /* Provide any AAD data. This can be called zero or more times as required */
    if(1 != EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len)) {
        handleOpenSSLError("");
        goto end;
    }
    /* Provide the message to be encrypted, and obtain the encrypted output.
     * EVP_EncryptUpdate can be called multiple times if necessary */
    /* encrypt in block lengths of 16 bytes */
    while(ciphertext_len <= plaintext_len-16) {
        if(1 != EVP_EncryptUpdate(ctx, ciphertext+ciphertext_len, &len, plaintext+ciphertext_len, 16))
        {
            handleOpenSSLError("");
            goto end;
        }
        ciphertext_len+=len;
    }
    if(1 != EVP_EncryptUpdate(ctx, ciphertext+ciphertext_len, &len, plaintext+ciphertext_len, plaintext_len-ciphertext_len)) {
        handleOpenSSLError("");
        goto end;
    }
    ciphertext_len+=len;
    /* Finalise the encryption. Normally ciphertext bytes may be written at
     * this stage, but this does not occur in GCM mode
     */
    if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + ciphertext_len, &len)) {
        handleOpenSSLError("");
        goto end;
    }
    ciphertext_len += len;
    /* Get the tag */
    if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag)) {
        handleOpenSSLError("");
        goto end;
    }
    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);
end:
    printf("%s", SeparatorLine);
    return ciphertext_len;
}

int decrypt_gcm(
    const EVP_CIPHER *cipher, unsigned char *ciphertext, int ciphertext_len, unsigned char *aad, int aad_len,
    unsigned char *key, unsigned char *iv, unsigned char *decryptedtext, unsigned char *tag)
{
    EVP_CIPHER_CTX *ctx;
    int len=0, decryptedtext_len=0, ret;
    /* Create and initialise the context */
    if(!(ctx = EVP_CIPHER_CTX_new())) {
        handleOpenSSLError("");
        goto end;
    }
    /* Initialise the decryption operation. */
    if(!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) {
        handleOpenSSLError("");
        goto end;
    }
    /* Provide any AAD data. This can be called zero or more times as
     * required */
    if(!EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len)) {
        handleOpenSSLError("");
        goto end;
    }
    /* Provide the message to be decrypted, and obtain the decryptedtext output.
     * EVP_DecryptUpdate can be called multiple times if necessary
     */
     while(decryptedtext_len <= ciphertext_len-16)
     {
        if(1!=EVP_DecryptUpdate(ctx, decryptedtext+decryptedtext_len, &len, ciphertext+decryptedtext_len, 16))
        {
            handleOpenSSLError("");
            goto end;
        }
        decryptedtext_len+=len;
    }
    if(1!=EVP_DecryptUpdate(ctx, decryptedtext+decryptedtext_len, &len, ciphertext+decryptedtext_len, ciphertext_len-decryptedtext_len))
    {
        handleOpenSSLError("");
        goto end;
    }
    decryptedtext_len+=len;
    /* Set expected tag value. Works in OpenSSL 1.0.1d and later */
    if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag)) {
        handleOpenSSLError("");
        goto end;
    }
    /* Finalise the decryption. A positive return value indicates success,
     * anything else is a failure - the plaintext is not trustworthy.
     */
    ret = EVP_DecryptFinal_ex(ctx, decryptedtext + decryptedtext_len, &len);
    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);
    if(ret > 0)
    {
        /* Success */
        //decryptedtext_len += len;
        return decryptedtext_len;
    } else {
        /* Verify failed */
        handleError("AES-GCM decryption failed");
        return -1;
    }
end:

    printf("%s", SeparatorLine);
    return 0;
}

void TestAesGcmCipher(
    const char* ciphername, const EVP_CIPHER *cipher, unsigned char *key, int key_length,
    unsigned char *iv, int iv_length, unsigned char *aad, int aad_length, unsigned char* plaintext,
    int plaintext_len, unsigned char *expected_tag=NULL, int expected_tag_length=0)
{
    unsigned char ciphertext[8192];
    int ciphertext_len = 0;
    unsigned char decryptedtext[8300];
    int decryptedtext_len = 0;
    unsigned char tag[100];
    printf("\nTestAesGcmCipher: %s\n\n", ciphername);
    printf("Key Bytes:\n");
    BIO_dump_fp (stdout, (const char *)key, key_length);
    printf("IV:\n");
    BIO_dump_fp (stdout, (const char *)iv, iv_length);
    printf("AAD:\n");
    BIO_dump_fp (stdout, (const char *)aad, aad_length);
    printf("PlainText:\n");
    BIO_dump_fp (stdout, (const char *)plaintext, plaintext_len);
    /* Encrypt the plaintext */
    ciphertext_len = encrypt_gcm(cipher, plaintext, plaintext_len, aad, aad_length, key, iv, ciphertext, tag);
    /* Check the tag matches if not NULL */
    if (expected_tag != NULL &&
        memcmp(tag, expected_tag, expected_tag_length))
    {
        handleError("Expected tag not produced by encryption\n");
    }
    /* Do something useful with the ciphertext here */
    printf("Ciphertext:\n");
    BIO_dump_fp (stdout, (const char *)ciphertext, ciphertext_len);
    printf("Tag:\n");
    BIO_dump_fp (stdout, (const char *)tag, 16);
    /* Decrypt the ciphertext */
    decryptedtext_len = decrypt_gcm(cipher, ciphertext, ciphertext_len, aad, aad_length, key, iv, decryptedtext, tag);
    /* Show the decrypted text */
    printf("DecryptedText:\n");
    BIO_dump_fp (stdout, (const char *)decryptedtext, decryptedtext_len);
    if (decryptedtext_len != plaintext_len ||
        memcmp(plaintext, decryptedtext, decryptedtext_len) != 0)
    {
        handleError("PlainText and DecryptedText don't match\n");
    }
    else
    {
        printf("PlainText and DecryptedText match\n");
    }

    printf("%s", SeparatorLine);
    return;
}

void
TestAesGcmGeneric()
{
    unsigned char plaintext[8192];
    int plaintext_len = 70;
    unsigned char iv[12]; // Symcrypt only support 12 byte Nonce for GCM
    unsigned char key[32];
    unsigned char aad[32];

    while(!RAND_bytes(key, 32));
    while(!RAND_bytes(iv, 12));
    while(!RAND_bytes(aad, 32));
    while(!RAND_bytes(plaintext, plaintext_len));

    TestAesGcmCipher("EVP_aes_128_gcm", EVP_aes_128_gcm(), key, 16, iv, 12, aad, 16, plaintext, plaintext_len);
    TestAesGcmCipher("EVP_aes_192_gcm", EVP_aes_192_gcm(), key, 24, iv, 12, aad, 16, plaintext, plaintext_len);
    TestAesGcmCipher("EVP_aes_256_gcm", EVP_aes_256_gcm(), key, 32, iv, 12, aad, 16, plaintext, plaintext_len);

    TestAesGcmCipher("EVP_aes_256_gcm", EVP_aes_256_gcm(), gcm_key, 32, gcm_iv, 12, gcm_aad, 16, gcm_pt, 16, gcm_tag, 16);

    printf("%s", SeparatorLine);
    return;
}

void TestCiphers()
{
    TestAesCbc();
    TestAesEcb();
    TestAesGcmGeneric();
    TestAesXts();
    printf("%s", SeparatorLine);

}

void TestHKDF(void)
{
    EVP_PKEY_CTX *pctx;
    unsigned char out[20];
    size_t outlen;
    int i;
    unsigned char salt[] = "0123456789";
    unsigned char key[] = "012345678901234567890123456789";
    unsigned char info[] = "infostring";
    const unsigned char expected[] = {
        0xe5, 0x07, 0x70, 0x7f, 0xc6, 0x78, 0xd6, 0x54, 0x32, 0x5f, 0x7e, 0xc5,
        0x7b, 0x59, 0x3e, 0xd8, 0x03, 0x6b, 0xed, 0xca
    };
    size_t expectedlen = sizeof(expected);

    printf("\n Testing HKDF \n\n");

    printf("Command EVP_PKEY_CTX_new_id\n");
    pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
    if (pctx == NULL) {
        handleOpenSSLError("");
        goto end;
    }

    /* We do this twice to test reuse of the EVP_PKEY_CTX */
    for (i = 0; i < 2; i++) {
        outlen = sizeof(out);
        memset(out, 0, outlen);

        printf("Command EVP_PKEY_derive_init\n");
        if (EVP_PKEY_derive_init(pctx) <= 0)
        {
            handleOpenSSLError("EVP_PKEY_derive_init");
            goto end;
        }
        printf("Command EVP_PKEY_CTX_set_hkdf_md\n");
        if (EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha256()) <= 0) {
            handleOpenSSLError("EVP_PKEY_CTX_set_hkdf_md");
            goto end;
        }
        printf("Command EVP_PKEY_CTX_set1_hkdf_salt\n");
        if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, sizeof(salt) - 1) <= 0) {
            handleOpenSSLError("EVP_PKEY_CTX_set1_hkdf_salt");
            goto end;
        }
        printf("Command EVP_PKEY_CTX_set1_hkdf_key\n");
        if (EVP_PKEY_CTX_set1_hkdf_key(pctx, key, sizeof(key) - 1) <= 0) {
            handleOpenSSLError("EVP_PKEY_CTX_set1_hkdf_key");
            goto end;
        }
        printf("Command EVP_PKEY_CTX_add1_hkdf_info\n");
        if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, sizeof(info) - 1) <= 0) {
            handleOpenSSLError("EVP_PKEY_CTX_add1_hkdf_info");
            goto end;
        }
        printf("Command EVP_PKEY_derive\n");
        if (EVP_PKEY_derive(pctx, out, &outlen) <= 0) {
            handleOpenSSLError("EVP_PKEY_derive");
            goto end;
        }

        printBytes((char *)out, outlen, "Output KDF");
        printBytes((char *)expected, expectedlen, "Expected KDF");

        if ((outlen != expectedlen) ||
            (memcmp(out, expected, expectedlen) != 0))
        {
            handleError("KDF didn't derive the expected values\n");
        }
        else {
            printf("KDF produced the right value\n");
        }
    }

 end:
    EVP_PKEY_CTX_free(pctx);
    printf("%s", SeparatorLine);
    return;
}

void TestTls1Prf(void)
{
    EVP_PKEY_CTX *pctx;
    unsigned char out[16];
    size_t outlen = sizeof(out);
    int i;
    const unsigned char expected[sizeof(out)] = {
        0x8e, 0x4d, 0x93, 0x25, 0x30, 0xd7, 0x65, 0xa0,
        0xaa, 0xe9, 0x74, 0xc3, 0x04, 0x73, 0x5e, 0xcc
    };
    size_t expectedlen = sizeof(expected);

    printf("\n Testing TLS1PRF \n\n");

    pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL);

    /* We do this twice to test reuse of the EVP_PKEY_CTX */
    for (i = 0; i < 2; i++) {
        outlen = sizeof(out);
        memset(out, 0, outlen);

        if (EVP_PKEY_derive_init(pctx) <= 0) {
            handleOpenSSLError("EVP_PKEY_derive_init");
            goto end;
        }
        if (EVP_PKEY_CTX_set_tls1_prf_md(pctx, EVP_sha256()) <= 0) {
            handleOpenSSLError("EVP_PKEY_CTX_set_tls1_prf_md");
            goto end;
        }
        if (EVP_PKEY_CTX_set1_tls1_prf_secret(pctx, reinterpret_cast<const unsigned char*>("secret"), 6) <= 0) {
            handleOpenSSLError("EVP_PKEY_CTX_set1_tls1_prf_secret");
            goto end;
        }
        if (EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, reinterpret_cast<const unsigned char*>("seed"), 4) <= 0) {
            handleOpenSSLError("EVP_PKEY_CTX_add1_tls1_prf_seed");
            goto end;
        }
        if (EVP_PKEY_derive(pctx, out, &outlen) <= 0) {
            handleOpenSSLError("EVP_PKEY_derive");
            goto end;
        }

        if ((outlen != expectedlen) ||
            (memcmp(out, expected, expectedlen) != 0))
        {
            handleError("TLS1Prf didn't derive the expected values\n");
        }
        else {
            printf("TLS1Prf derived the expected value\n");
        }
    }

end:
    EVP_PKEY_CTX_free(pctx);
    printf("%s", SeparatorLine);
    return;
}


void TestHMAC(void)
{
    const unsigned char pbKey[] = {
        0x0a, 0x71, 0xd5, 0xcf, 0x99, 0x84, 0x9b, 0xc1, 0x3d, 0x73, 0x83, 0x2d, 0xcd, 0x86, 0x42, 0x44
    };

    const unsigned char pbMsg[] = {
        0x17, 0xf1, 0xee, 0x0c, 0x67, 0x67, 0xa1, 0xf3, 0xf0, 0x4b, 0xb3, 0xc1, 0xb7, 0xa4, 0xe0, 0xd4,
        0xf0, 0xe5, 0x9e, 0x59, 0x63, 0xc1, 0xa3, 0xbf, 0x15, 0x40, 0xa7, 0x6b, 0x25, 0x13, 0x6b, 0xae,
        0xf4, 0x25, 0xfa, 0xf4, 0x88, 0x72, 0x2e, 0x3e, 0x33, 0x1c, 0x77, 0xd2, 0x6f, 0xbb, 0xd8, 0x30,
        0x0d, 0xf5, 0x32, 0x49, 0x8f, 0x50, 0xc5, 0xec, 0xd2, 0x43, 0xf4, 0x81, 0xf0, 0x93, 0x48, 0xf9,
        0x64, 0xdd, 0xb8, 0x05, 0x6f, 0x6e, 0x28, 0x86, 0xbb, 0x5b, 0x2f, 0x45, 0x3f, 0xcf, 0x1d, 0xe5,
        0x62, 0x9f, 0x3d, 0x16, 0x63, 0x24, 0x57, 0x0b, 0xf8, 0x49, 0x79, 0x2d, 0x35, 0xe3, 0xf7, 0x11,
        0xb0, 0x41, 0xb1, 0xa7, 0xe3, 0x04, 0x94, 0xb5, 0xd1, 0x31, 0x64, 0x84, 0xed, 0x85, 0xb8, 0xda,
        0x37, 0x09, 0x46, 0x27, 0xa8, 0xe6, 0x60, 0x03, 0xd0, 0x79, 0xbf, 0xd8, 0xbe, 0xaa, 0x80, 0xdc,
    };

    const unsigned char pbExpected[] = {
        0x2a, 0x0f, 0x54, 0x20, 0x90, 0xb5, 0x1b, 0x84, 0x46, 0x5c, 0xd9, 0x3e, 0x5d, 0xde, 0xea, 0xa1,
        0x4c, 0xa5, 0x11, 0x62, 0xf4, 0x80, 0x47, 0x83, 0x5d, 0x2d, 0xf8, 0x45, 0xfb, 0x48, 0x8a, 0xf4
    };

    unsigned char pbOutput[sizeof(pbExpected)];

    const EVP_MD *md = NULL;
    EVP_MD_CTX *mdctx = NULL, *mdctxCopy = NULL;
    EVP_PKEY *pkey = NULL;
    EVP_PKEY_CTX *pctx = NULL;
    size_t cbOutputLen;
    int ret;

    printf("Testing HMAC\n");

    /* We do this multiple times to test reuse of the EVP_PKEY_CTX */
    for (int i = 0; i < 2; i++) {

        md = EVP_sha256();

        mdctx = EVP_MD_CTX_new();
        mdctxCopy = EVP_MD_CTX_new();

        if(mdctx == NULL || mdctxCopy == NULL) {
            handleOpenSSLError("EVP_MD_CTX_new");
            goto end;
        }

        pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, pbKey, sizeof(pbKey));

        if(pkey == NULL) {
            handleOpenSSLError("EVP_PKEY_new_raw_private_key");
            goto end;
        }

        if((ret = EVP_DigestSignInit(mdctx, &pctx, md, NULL, pkey)) <= 0) {
            printf("EVP_DigestSignInit returned %d\n", ret);
            handleOpenSSLError("EVP_DigestSignInit");
            goto end;
        }

        // make a copy of mdctx for another hmac computation with the same key
        if (EVP_MD_CTX_copy_ex(mdctxCopy, mdctx) <= 0) {
            handleOpenSSLError("EVP_MD_CTX_copy_ex");
            goto end;
        }

        if(i & 1) {

            // update message one byte at a time
            for(int j = 0; j < sizeof(pbMsg); j++)
            {
                if(EVP_DigestSignUpdate(mdctx, pbMsg + j, 1) <= 0) {
                    handleOpenSSLError("EVP_DigestSignUpdate");
                    goto end;
                }
            }
        }
        else {

            // update message with a single call
            if(EVP_DigestSignUpdate(mdctx, pbMsg, sizeof(pbMsg)) <= 0) {
                handleOpenSSLError("EVP_DigestSignUpdate");
                goto end;
            }
        }

        memset(pbOutput, 0, sizeof(pbOutput));
        cbOutputLen = sizeof(pbOutput);

        if(EVP_DigestSignFinal(mdctx, pbOutput, &cbOutputLen) <= 0) {
            handleOpenSSLError("EVP_DigestSignFinal");
            goto end;
        }

        if ((cbOutputLen != sizeof(pbExpected)) || (memcmp(pbOutput, pbExpected, sizeof(pbExpected)) != 0)) {

            handleError("HMAC did not generate the expected value\n");
        }
        else {

            printf("HMAC generated the expected value\n");
        }

        //
        // recompute the tag by making use of the copied EVP_MD_CTX without doing key expansion
        //
        if(EVP_DigestSignUpdate(mdctxCopy, pbMsg, sizeof(pbMsg)) <= 0) {
            handleOpenSSLError("EVP_DigestSignUpdate");
            goto end;
        }

        memset(pbOutput, 0, sizeof(pbOutput));
        cbOutputLen = sizeof(pbOutput);

        if(EVP_DigestSignFinal(mdctxCopy, pbOutput, &cbOutputLen) <= 0) {
            handleOpenSSLError("EVP_DigestSignFinal");
            goto end;
        }

        if ((cbOutputLen != sizeof(pbExpected)) || (memcmp(pbOutput, pbExpected, sizeof(pbExpected)) != 0)) {

            handleError("HMAC did not generate the second expected value\n");
        }
        else {

            printf("HMAC generated the second expected value\n");
        }

        EVP_MD_CTX_free(mdctx);
        EVP_MD_CTX_free(mdctxCopy);
        EVP_PKEY_free(pkey);

        mdctx = NULL;
        mdctxCopy = NULL;
        pkey = NULL;
        pctx = NULL;
    }

 end:

    EVP_MD_CTX_free(mdctx);
    EVP_MD_CTX_free(mdctxCopy);
    EVP_PKEY_free(pkey);
    EVP_PKEY_CTX_free(pctx);

    printf("%s", SeparatorLine);
    return;
}


#ifdef SCOSSL_SSHKDF

//
// Allocate memory and decode an input string in hexadecimal form into the allocated buffer
//
// Parameters
//      str : Pointer to the hex string containing data, must be of even size.
//      len : Size of the str buffer on input (excluding the null terminator), size of the decoded buffer on output (i.e., half the input buffer size)
//
// Returns
//      Pointer to the allocated buffer containing the decoded data on success, NULL otherwise.
//
unsigned char* scossl_decode_hexstr(const char *str, size_t *len)
{
    unsigned char* buf = NULL;
    int bValidInput = 1;
    size_t buf_len = (*len) / 2;

    if(!str || (*len & 1))
        goto done;

    buf = (unsigned char*)OPENSSL_malloc(buf_len);

    if(!buf)
    {
        *len = 0;
        goto done;
    }

    for(size_t i = 0; i < buf_len && bValidInput; i++)
    {
        unsigned char val = 0;
        unsigned char ch;

        for(int j = 0; j < 2; j++)
        {
            val <<= 4;

            ch = (unsigned char)str[2 * i + j];
            if(ch >= '0' && ch <= '9')
                val |= ch - '0';
            else if (ch >= 'a' && ch <= 'f')
                val |= ch  - 'a' + 10;
            else if (ch >= 'A' && ch <= 'F')
                val |= ch  - 'A' + 10;
            else
                bValidInput = 0;
        }

        buf[i] = val;
    }

    if(bValidInput)
    {
        *len  = buf_len;
    }
    else
    {
        *len = 0;
        OPENSSL_free(buf);
        buf = NULL;
    }

done:
    return buf;
}


typedef struct _scossl_sshkdf_testvector
{
    // KDF inputs
    const EVP_MD*   md;
    const char*     md_name;
    const char*     szSharedSecret;
    unsigned char*  pbSharedSecret;
    size_t          cbSharedSecret;
    const char*     szHashValue;
    unsigned char*  pbHashValue;
    size_t          cbHashValue;
    const char*     szSessionId;
    unsigned char*  pbSessionId;
    size_t          cbSessionId;

    // KDF outputs
    unsigned char*  pbDerivedKeys[6];
    size_t          cbDerivedKeys[6];

} SCOSSL_SSHKDF_TEST_VECTOR, *PSCOSSL_SSHKDF_TEST_VECTOR;

void scossl_sshkdf_testvector_free(PSCOSSL_SSHKDF_TEST_VECTOR ptv)
{
    if(ptv)
    {
        OPENSSL_free(ptv->pbSharedSecret);
        OPENSSL_free(ptv->pbHashValue);
        OPENSSL_free(ptv->pbSessionId);

        for(int i = 0; i < sizeof(ptv->pbDerivedKeys) / sizeof(ptv->pbDerivedKeys[0]); i++)
            OPENSSL_free(ptv->pbDerivedKeys[i]);

        OPENSSL_free(ptv);
    }
}


PSCOSSL_SSHKDF_TEST_VECTOR scossl_sshkdf_testvector_new(const EVP_MD *md, const char *md_name,
                                                        const char *szSharedSecret, size_t lenSharedSecret,
                                                        const char *szHashValue, size_t lenHashValue,
                                                        const char *szSessionId, size_t lenSessionId,
                                                        const char **szDerivedKeys,
                                                        const size_t *lenDerivedKeys)
{
    bool success = true;
    PSCOSSL_SSHKDF_TEST_VECTOR ptv = NULL;

    ptv = (PSCOSSL_SSHKDF_TEST_VECTOR)OPENSSL_zalloc(sizeof(*ptv));

    if(!ptv)
        goto end;

    ptv->md = md;
    ptv->md_name = md_name;

    ptv->szSharedSecret = szSharedSecret;
    // Set the length field to the string length first, will contain data length after the scossl_decode_hexstr() call.
    // Same for szHashValue and szSessionId fields as well.
    ptv->cbSharedSecret = lenSharedSecret;
    ptv->pbSharedSecret = scossl_decode_hexstr(szSharedSecret, &ptv->cbSharedSecret);

    ptv->szHashValue = szHashValue;
    ptv->cbHashValue = lenHashValue;
    ptv->pbHashValue = scossl_decode_hexstr(szHashValue, &ptv->cbHashValue);

    ptv->szSessionId = szSessionId;
    ptv->cbSessionId = lenSessionId;
    ptv->pbSessionId = scossl_decode_hexstr(szSessionId, &ptv->cbSessionId);

    if(!ptv->pbSharedSecret || !ptv->pbHashValue || !ptv->pbSessionId)
    {
        success = false;
        goto end;
    }

    for(int i = 0; i < sizeof(ptv->pbDerivedKeys) / sizeof(ptv->pbDerivedKeys[0]); i++)
    {
        ptv->cbDerivedKeys[i] = lenDerivedKeys[i];
        ptv->pbDerivedKeys[i] = scossl_decode_hexstr(szDerivedKeys[i], &ptv->cbDerivedKeys[i]);

        if(!ptv->pbDerivedKeys[i])
        {
            success = false;
            break;
        }
    }

end:

    if(!success && ptv)
    {
        scossl_sshkdf_testvector_free(ptv);
        ptv = NULL;
    }

    return ptv;
}


extern "C" {
extern EVP_KDF_CTX* e_scossl_EVP_KDF_CTX_new_id(int id);
}

void TestSshKdf(void)
{
    const size_t MAX_DERIVED_KEY = 64;
    unsigned char derivedKey[MAX_DERIVED_KEY];
    int ret;

    // hash = "SHA-1"
    // shared secret length = 1024
    // IV length = 64
    // encryption key length = 192
    const char szSharedSecret1[] = "0000008055bae931c07fd824bf10add1902b6fbc7c665347383498a686929ff5a25f8e40cb6645ea814fb1a5e0a11f852f86255641e5ed986e83a78bc8269480eac0b0dfd770cab92e7a28dd87ff452466d6ae867cead63b366b1c286e6c4811a9f14c27aea14c5171d49b78c06e3735d36e6a3be321dd5fc82308f34ee1cb17fba94a59";
    const char szHashValue1[] = "a4ebd45934f56792b5112dcd75a1075fdc889245";
    const char szSessionId1[] = "a4ebd45934f56792b5112dcd75a1075fdc889245";
    const char szInitialIVClientToServer1[] = "e2f627c0b43f1ac1";
    const char szInitialIVServerToClient1[] = "58471445f342b181";
    const char szEncryptionKeyClientToServer1[] = "1ca9d310f86d51f6cb8e7007cb2b220d55c5281ce680b533";
    const char szEncryptionKeyServerToClient1[] = "2c60df8603d34cc1dbb03c11f725a44b44008851c73d6844";
    const char szIntegrityKeyClientToSever1[] = "472eb8a26166ae6aa8e06868e45c3b26e6eeed06";
    const char szIntegrityKeyServerToClient1[] = "e3e2fdb9d7bc21165a3dbe47e1eceb7764390bab";

    const char *szDerivedKeys1[] = {
        szInitialIVClientToServer1,
        szInitialIVServerToClient1,
        szEncryptionKeyClientToServer1,
        szEncryptionKeyServerToClient1,
        szIntegrityKeyClientToSever1,
        szIntegrityKeyServerToClient1
    };

    size_t lenDerivedKeys1[] = {
        sizeof(szInitialIVClientToServer1) - 1,
        sizeof(szInitialIVServerToClient1) - 1,
        sizeof(szEncryptionKeyClientToServer1) - 1,
        sizeof(szEncryptionKeyServerToClient1) - 1,
        sizeof(szIntegrityKeyClientToSever1) - 1,
        sizeof(szIntegrityKeyServerToClient1) - 1
    };

    // hash = "SHA-256"
    // shared secret length = 1024
    // IV length = 64
    // encryption key length = 192
    const char szSharedSecret2[] = "0000008100875c551cef526a4a8be1a7df27e9ed354bac9afb71f53dbae905679d14f9faf2469c53457cf80a366be278965ba6255276ca2d9f4a97d271f71e50d8a9ec46253a6a906ac2c5e4f48b27a63ce08d80390a492aa43bad9d882ccac23dac88bcada4b4d426a362083dab6569c54c224dd2d87643aa227693e141ad1630ce13144e";
    const char szHashValue2[] = "0e683fc8a9ed7c2ff02def23b2745ebc99b267daa86a4aa7697239088253f642";
    const char szSessionId2[] = "0e683fc8a9ed7c2ff02def23b2745ebc99b267daa86a4aa7697239088253f642";
    const char szInitialIVClientToServer2[] = "41ff2ead1683f1e6";
    const char szInitialIVServerToClient2[] = "e619ecfd9edb50cd";
    const char szEncryptionKeyClientToServer2[] = "4a6314d2f7511bf88fad39fb6892f3f218cafd530e72fe43";
    const char szEncryptionKeyServerToClient2[] = "084c15fb7f99c65ff134eeb407cee5d540c341dea45a42a5";
    const char szIntegrityKeyClientToSever2[] = "41ec5a94fecce7707ea156a6ad29239a891621adacbedb8be70675008d6f9274";
    const char szIntegrityKeyServerToClient2[] = "47d3c20aba60981e47b30533623613ff1cacbcf1642fb4ad86ee712f2aed9af8";

    const char *szDerivedKeys2[] = {
        szInitialIVClientToServer2,
        szInitialIVServerToClient2,
        szEncryptionKeyClientToServer2,
        szEncryptionKeyServerToClient2,
        szIntegrityKeyClientToSever2,
        szIntegrityKeyServerToClient2
    };

    size_t lenDerivedKeys2[] = {
        sizeof(szInitialIVClientToServer2) - 1,
        sizeof(szInitialIVServerToClient2) - 1,
        sizeof(szEncryptionKeyClientToServer2) - 1,
        sizeof(szEncryptionKeyServerToClient2) - 1,
        sizeof(szIntegrityKeyClientToSever2) - 1,
        sizeof(szIntegrityKeyServerToClient2) - 1
    };


    PSCOSSL_SSHKDF_TEST_VECTOR test_vectors[2] = {nullptr, nullptr};
    EVP_KDF_CTX *kdf_ctx = nullptr;

    printf("Testing SSH-KDF\n");

    test_vectors[0] = scossl_sshkdf_testvector_new(EVP_sha1(), "sha1",
                                                    szSharedSecret1, sizeof(szSharedSecret1) - 1,
                                                    szHashValue1, sizeof(szHashValue1) - 1,
                                                    szSessionId1, sizeof(szSessionId1) - 1,
                                                    szDerivedKeys1,
                                                    lenDerivedKeys1);

    if(!test_vectors[0])
    {
        handleError("scossl_sshkdf_testvector_new");
        goto end;
    }

    test_vectors[1] = scossl_sshkdf_testvector_new(EVP_sha256(), "sha256",
                                                    szSharedSecret2, sizeof(szSharedSecret2) - 1,
                                                    szHashValue2, sizeof(szHashValue2) - 1,
                                                    szSessionId2, sizeof(szSessionId2) - 1,
                                                    szDerivedKeys2,
                                                    lenDerivedKeys2);

    if(!test_vectors[1])
    {
        handleError("scossl_sshkdf_testvector_new");
        goto end;
    }

    //
    // Test both EVP_KDF_ctrl and EVP_KDF_ctrl_str functions.
    // bCtrlStrMode = 0 uses EVP_KDF_ctrl functions and
    // bCtrlStrMode = 1 uses EVP_KDF_ctrl_str functions
    //
    for(int bCtrlStrMode = 0; bCtrlStrMode < 2; bCtrlStrMode++)
    {
        printf("testing %s mode\n", bCtrlStrMode ? "ctrl_str" : "ctrl");

        for(int i = 0; i < sizeof(test_vectors) / sizeof(test_vectors[0]); i++)
        {
            PSCOSSL_SSHKDF_TEST_VECTOR ptv = test_vectors[i];

            kdf_ctx = e_scossl_EVP_KDF_CTX_new_id(EVP_KDF_SSHKDF);

            if(!kdf_ctx)
            {
                handleOpenSSLError("EVP_KDF_CTX_new_id");
                break;
            }

            if(bCtrlStrMode)
                ret = EVP_KDF_ctrl_str(kdf_ctx, "digest", ptv->md_name);
            else
                ret = EVP_KDF_ctrl(kdf_ctx, EVP_KDF_CTRL_SET_MD, ptv->md);

            if(ret <= 0)
            {
                handleOpenSSLError("EVP_KDF_ctrl(EVP_KDF_CTRL_SET_MD)");
                break;
            }

            if(bCtrlStrMode)
                ret = EVP_KDF_ctrl_str(kdf_ctx, "hexkey", ptv->szSharedSecret);
            else
                ret = EVP_KDF_ctrl(kdf_ctx, EVP_KDF_CTRL_SET_KEY, ptv->pbSharedSecret, ptv->cbSharedSecret);

            if(ret <= 0)
            {
                handleOpenSSLError("EVP_KDF_ctrl(EVP_KDF_CTRL_SET_KEY)");
                break;
            }

            if(bCtrlStrMode)
                ret = EVP_KDF_ctrl_str(kdf_ctx, "hexxcghash", ptv->szHashValue);
            else
                ret = EVP_KDF_ctrl(kdf_ctx, EVP_KDF_CTRL_SET_SSHKDF_XCGHASH, ptv->pbHashValue, ptv->cbHashValue);

            if(ret <= 0)
            {
                handleOpenSSLError("EVP_KDF_ctrl(EVP_KDF_CTRL_SET_SSHKDF_XCGHASH)");
                break;
            }

            if(bCtrlStrMode)
                ret = EVP_KDF_ctrl_str(kdf_ctx, "hexsession_id", ptv->szSessionId);
            else
                ret = EVP_KDF_ctrl(kdf_ctx, EVP_KDF_CTRL_SET_SSHKDF_SESSION_ID, ptv->pbSessionId, ptv->cbSessionId);

            if(ret <= 0)
            {
                handleOpenSSLError("EVP_KDF_ctrl(EVP_KDF_CTRL_SET_SSHKDF_SESSION_ID)");
                break;
            }

            for(int keyIndex = 0; keyIndex < sizeof(ptv->cbDerivedKeys) / sizeof(ptv->cbDerivedKeys[0]); keyIndex++)
            {
                if(bCtrlStrMode)
                {
                    char szLabel[] = { (char)('A' + keyIndex), 0};
                    ret = EVP_KDF_ctrl_str(kdf_ctx, "type", szLabel);
                }
                else
                    ret = EVP_KDF_ctrl(kdf_ctx, EVP_KDF_CTRL_SET_SSHKDF_TYPE, 0x41 + keyIndex);

                if(ret <= 0)
                {
                    handleOpenSSLError("EVP_KDF_ctrl(EVP_KDF_CTRL_SET_SSHKDF_TYPE)");
                    break;
                }

                OPENSSL_cleanse(derivedKey, sizeof(derivedKey));

                if(EVP_KDF_derive(kdf_ctx, derivedKey, ptv->cbDerivedKeys[keyIndex]) <= 0)
                {
                    handleOpenSSLError("EVP_KDF_derive");
                    break;
                }

                if(memcmp(derivedKey, ptv->pbDerivedKeys[keyIndex], ptv->cbDerivedKeys[keyIndex]) != 0)
                {
                    handleError("SSHKDF test vector mismatch");
                }
            }

            EVP_KDF_CTX_free(kdf_ctx);
            kdf_ctx = NULL;
        }
    }

end:

    if(kdf_ctx)
        EVP_KDF_CTX_free(kdf_ctx);

    for(int i = 0; i < sizeof(test_vectors) / sizeof(test_vectors[0]); i++)
        scossl_sshkdf_testvector_free(test_vectors[i]);

    printf("SSH-KDF succeeded\n");
    printf("%s", SeparatorLine);
}

#endif // SCOSSL_SSHKDF

int main(int argc, char** argv)
{
#if OPENSSL_VERSION_MAJOR == 3
    OSSL_PROVIDER *symcrypt_provider = NULL;
#endif
    int scossl_log_level = SCOSSL_LOG_LEVEL_NO_CHANGE;
    int scossl_ossl_ERR_level = SCOSSL_LOG_LEVEL_NO_CHANGE;
    bool useEngine = true;

    for (int i = 1; i < argc; i++)
    {
        if (strcmp(argv[i], "--log-level") == 0)
        {
            if (argc < ++i)
            {
                printf("Missing log level");
                return 0;
            }

            scossl_log_level = atoi(argv[i]);
        }
        else if (strcmp(argv[i], "--err-level") == 0)
        {
            if (argc < ++i)
            {
                printf("Missing log level");
                return 0;
            }

            scossl_ossl_ERR_level = atoi(argv[i]);
        }
#if OPENSSL_VERSION_MAJOR == 3
        else if (strcmp(argv[i], "--provider-path") == 0)
        {
            if (argc < ++i)
            {
                printf("Missing provider path");
                return 0;
            }

            if (!OSSL_PROVIDER_set_default_search_path(NULL, argv[i]))
            {
                {
                    handleOpenSSLError("Failed to set provider path");
                    return 0;
                }
            }

            if ((symcrypt_provider = OSSL_PROVIDER_load(NULL, "symcryptprovider")) == NULL)
            {
                handleOpenSSLError("Failed to load symcrypt provider");
                return 0;
            }
        }
        else if (strcmp(argv[i], "--no-engine") == 0)
        {
            useEngine = false;
        }
#endif
        else if (strcmp(argv[i], "--help") == 0)
        {
            printf("Usage: SslPlay <options>\n");
            printf("Options:\n");
            printf("  --log-level <log level>           Specify the SCOSSL logging level\n");
            printf("  --err-level <err level>           Specify the SCOSSL error logging level\n");
#if OPENSSL_VERSION_MAJOR == 3
            printf("  --provider-path <provider path>   Specify a directory to locate the symcrypt provider\n");
            printf("  --no-engine                       Disable the SymCrypt engine for testing\n");
#endif
            return 0;
        }
    }

    SCOSSL_set_trace_level(scossl_log_level, scossl_ossl_ERR_level);
    if (useEngine)
    {
        SCOSSL_ENGINE_Initialize();
    }
    bio_err = BIO_new_fp(stdout, BIO_NOCLOSE);

    TestDigests(useEngine);
    TestCiphers();
    TestHKDF();
    TestTls1Prf();
    TestRsaEvpAll();
    TestEcc();
    TestDh();
    TestHMAC();

#ifdef SCOSSL_SSHKDF
    TestSshKdf();
#endif

#if OPENSSL_VERSION_MAJOR == 3
    OSSL_PROVIDER_unload(symcrypt_provider);
#endif

    BIO_free(bio_err);
    return 0;
}
